#include "CanonicalConv.h"
#include "Statistics.h"
#include <iostream>
#include <algorithm>
//#define STATS

using namespace std;

CanonicalConv::CanonicalConv()
{
}

CanonicalConv::~CanonicalConv()
{
}

/**
 * Returns the path of the URL, that is whatever is after the domain.
 */
string CanonicalConv::getPath(const string baseUrl)
{
	string path="";
	string protocol="";
	int posProtocol = baseUrl.find("://");
	if(posProtocol == string::npos)
		posProtocol = 0;
/**	path = baseUrl.substr(posProtocol+3,baseUrl.length()); */
	if((posProtocol + 3) < baseUrl.length())
		path = baseUrl.substr(posProtocol+3);
//	cout << "&&GeT PATH: " << path <<endl;
	int posPath = path.find("/");
	if(posPath == string::npos)
		posPath=path.length();
	path = path.substr(posPath);
//	cout << "&&GeT PATH: " << path <<endl;
	return path;
	
}

string CanonicalConv::getProtocol(const string baseUrl)
{
	/**
	 * Returns the protocol used in the URL, if there's none
	 * returns http:// as default!
	 */
	
	string protocol;
	int posProtocol = baseUrl.find("://");
	if(posProtocol == string::npos)
		protocol = "http://";
	else
		protocol = baseUrl.substr(0,posProtocol+3);
	return protocol;
}

string CanonicalConv::getDomain(const string baseUrl)
{
	/**
	 * Returns the domain of the url aka. host of the page
	 */
	string domain="";
	string protocol;
	int posProtocol = baseUrl.find("://");
	if(posProtocol == string::npos)
		posProtocol = -3;
	if((posProtocol+3) < baseUrl.length())
		domain = baseUrl.substr(posProtocol+3);
	int posDomain = domain.find("/");
	if(posDomain == string::npos)
		posDomain=domain.length();
	if(posDomain < domain.length())
		domain = domain.substr(0,posDomain);
	return domain;
	
	
}

string CanonicalConv::getCanonicalProtocol(const string baseUrl)
{
	time_t start;
#ifdef STATS
	start = time(&start);
#endif
	string protocol = getProtocol(baseUrl);
	/* STEP 1 --> Treating the protocol
		 * 	STEP 1.1 --> Protocol to LowerCase
		 */	
		 transform( protocol.begin(), protocol.end(), protocol.begin(),(int(*)(int)) tolower);
#ifdef STATS
	time_t end = time(&end);
	pthread_mutex_lock(&Statistics::CC_Mutex);
	Statistics::CC_calls++;
	Statistics::CC_time += (end-start);
	pthread_mutex_unlock(&Statistics::CC_Mutex);
#endif
		 return protocol;

}

string CanonicalConv::getCanonicalDomain(const string baseUrl)
{
	time_t start;
#ifdef STATS
	start = time(&start);
#endif
	string domain = getDomain(baseUrl);
	/* STEP 1 --> Treating the domain
		 *  STEP 1.1 --> Domain to LowerCase
		 */ transform( domain.begin(), domain.end(), domain.begin(),(int(*)(int)) tolower);
		//    cout << "After 2.1, domain is: " << domain << endl;
		 
		 /* STEP 1.2 --> Removing the userInfo tag i.e. username:passwd@ that always come before
		  * the hosts DNS.
		  */ int posAux= domain.find("@");
		  	 if(posAux!= string::npos && posAux < domain.length())
		  		 domain = domain.substr(posAux);
		//  	cout << "After 2.2, domain is: " << domain << endl;
		 /* STEP 1.3 --> Removing the default http port that is, :80
		  */ posAux = domain.rfind(":80");
		  	 if(posAux != string::npos)
		  		 domain = domain.substr(0,posAux);
		//  	cout << "After 2.3, domain is: " << domain << endl;
		  	
		 /* STEP 1.4 --> Convert from IPv4 or IPv6 to hostname
		  * 
		  * Will not be implemented unless really necessary because
		  * if can only generate 3 entries/site on the queue and 
		  * require extra overhead and DNS queries.
		  * TODO: Define an strategy to deal with IPv4 and IPv6 as domains in URLs
		  */
#ifdef STATS
	time_t end = time(&end);
	pthread_mutex_lock(&Statistics::CC_Mutex);
	Statistics::CC_calls++;
	Statistics::CC_time += (end-start);
	pthread_mutex_unlock(&Statistics::CC_Mutex);
#endif
		  return domain;
}

string CanonicalConv::getCanonicalPath(const string baseUrl)
{
	time_t start;
#ifdef STATS
	start = time(&start);
#endif
	
	string path = getPath(baseUrl); 
	/* STEP 1 --> Treating the Path
		   */
		  		// Initialization
//	cout << "&& Inicial PATH: " << path <<endl;
				int posAux=0;
				transform( path.begin(), path.end(), path.begin(),(int(*)(int)) tolower);
		  	    if(path.rfind("/")==path.length()-1)
		  	    	path = path.substr(0,path.length()-1);
		  	  
		  	    
		   /*  STEP 1.1 --> Removing the fragments (#)
		   */ string chars = "?&;/.#%";
			  posAux = path.rfind("#");
			  while(posAux != string::npos)
			  {
				  int posAuxx = path.find_first_of(chars,posAux + 1);
				  if(posAuxx == string::npos)
					  posAuxx = path.length();
				  path = path.erase(posAux,posAuxx - posAux);
				  posAux = path.rfind("#");
			  }			  
		  /* STEP 1.2 --> Removing the queries (?) and it's parameters (&)
		   * 		Won't be implemented yet because queried urls can lead
		   * 		to different webpages. An strategy is to be defined.
		   * 		In the meanwhile, let's just delete them!!
		   * 		TODO: Define how to deal with queries.
		   */
			  		  chars = ";#%";
			  		  posAux = path.rfind("?");
			  		  while(posAux != string::npos)
			  		  {
			  			  int posAuxx = path.find_first_of(chars,posAux + 1);
			  			  if(posAuxx == string::npos)
			  				  posAuxx = path.length();
			  			  path = path.erase(posAux,posAuxx - posAux);
			  			  posAux = path.rfind("?");
			  		  }
			  		  
			  		  posAux = path.rfind("&");
			  		  while(posAux != string::npos)
			  		  {
			  			  int posAuxx = path.find_first_of(chars,posAux + 1);
			  			  if(posAuxx == string::npos)
			  				  posAuxx = path.length();
			  			  path = path.erase(posAux,posAuxx - posAux);
			  			  posAux = path.rfind("&");
			  		  }
			  		  
			  
		  /* STEP 1.3 --> Removing the special char (;) 
		   */ 
			    chars = ";/.#%";	
			    posAux = path.rfind(";");
			  	while(posAux != string::npos)
			  	{
			  	  int posAuxx = path.find_first_of(chars,posAux + 1);
			  	  if(posAuxx == string::npos)
			  		  posAuxx = path.length();
			  	  path = path.erase(posAux,posAuxx - posAux);
			  	  posAux = path.rfind(";");
			    }
		
		  /* AUX STEP
		   * REmoving initial / from path
		   */
			  	
			//  	if(path.find("/") == 0)
			//  		path = path.substr(1,path.length()-1);
		   
			  	
			  	
			  	
		  /* STEP 1.4 --> Removing dotted sequencies (. or ..)
		   * 		Algortithm proposed at RFC 3986 pg.32
		   */   
//			  	cout << "&& Current PATH: " << path <<endl;
			  	string input = path.substr(0);
			  	string output = "";
			  	//cout << "&& Current Input: " << input <<endl;
			  	//cout << "&& Current Input: " << input <<endl;
			  	//cout << "&& Current Output: " << output <<endl;
			  		while( input.length() > 0 )
			  		{
		 		  	//cout << "&& Current Input: " << input <<endl;
			  	  	//cout << "&& Current Output: " << output <<endl;
			  			// Part A:
			  			//cout << " In Part A" << endl;
			  			//cout << "&& Current Input: " << input <<endl;
			  		  	if(input.find("../") == 0 || input.find("./")==0)
			  				input = input.erase(0,input.find("/"));
			  			else
			  			{
			  				// Part B
			  				//cout << " In Part B" << endl;
			  				//cout << "&& Current Input: " << input <<endl;
			  				if(input.find("/./") == 0 || (input.find("/.") ==0 && input.find("/..")!=0))
			  				  if(input.find("/./") == 0)
			  					  input = input.erase(0,2);
			  				  else
			  					  input = input.erase(1,1);
			  				else
			  				{
			  					//Part C
			  					//cout << " In Part C" << endl;
			  					//cout << "&& Current Input: " << input <<endl;
			  					if(input.find("/../") == 0 || input.find("/..")==0)
				  				{
				  				  if(input.find("/../") == 0)
				  					  input = input.erase(0,3);
				  				  else
				  					  input = input.erase(1,2);
				  				  output = output.erase(output.rfind("/"),output.length());
				  				}
				  				else
				  				{
				  					// Part D
				  					//cout << " In Part D" << endl;
				  					//cout << "&& Current Input: " << input <<endl;
				  					bool flag = true;
				  					for(int i=0;i< input.length();i++)
				  						if(input[i] != '.')
				  							flag = false;
				  					if(flag)
				  						input = "";
				  					else
				  					{
				  						// Part E
				  						//cout << " In Part E" << endl;
				  						//cout << "&& Current Input: " << input <<endl;
				  					    //cout << "&& Current Output: " << output <<endl;
				  						posAux = input.find("/",2);
				  						if(posAux == string::npos)
				  							posAux = input.length();
				  						//cout << "POSAUX: " << posAux << endl;
				  						output.append(input.substr(0,posAux));
				  						//cout << "&& Current Output: " << output <<endl;
				  						input = input.substr(posAux,input.length()-posAux);
				  		//				cout << "$#@$#@ INPUT: " << input <<endl; 
				  		//				cout << "dahdl" << endl;
			  						
				  					}
				  				}
				  				
			  				}
			  					
			  			}
			  	
			  			
			  		}
			  //		cout << "BLAH " << output << endl;
			  		path = output;
			  //		cout << " %#@%^^#$@ PATH: " << path << endl;
			  		
		  		/* STEP 1.5 --> Removing default pages filenames */
		  		
		  			/* Removing the "/index.html" */
	  			
	  			unsigned int  auxPointer = path.rfind("/index.html");
	  			if(auxPointer != string::npos)
	  			{
	  				path = path.substr(0,auxPointer);
	  			}
	  			else {
	  				
	  			/* Removing the "/index.htm" */
	  				
	  			auxPointer = path.rfind("/index.htm");
	  			if(auxPointer != string::npos)
	  			{
	  				path = path.substr(0,auxPointer);
	  			}
	  			else {
	  			
	  				/* Removing the "/default.aspx" */
	  				
	  			auxPointer = path.rfind("/default.aspx");
	  			if(auxPointer != string::npos)
	  			{
	  				path = path.substr(0,auxPointer);
	  			}
	  			else {
	  				
	  			/* Removing the "/default.asp" */
	  				
	  			auxPointer = path.rfind("/default.asp");
	  			if(auxPointer != string::npos)
	  			{
	  				path = path.substr(0,auxPointer);
	  			} } } }
	  		
		  		/** STEP 1.6 Setting UPPERCASE whatever is inside (%) for escape sequences
		  		 */
	  /*			posAux = path.find("%");
	  			while(posAux!= string::npos)
	  			{
	  				int posAuxx = path.find("%", posAux + 1);
	  				if(posAuxx!= string::npos)
	  					 transform(path.begin()+posAux,path.begin()+posAuxx,path.begin()+posAux,(int(*)(int)) toupper);
	  				posAux = path.find("%", posAuxx + 1);
	  				
	  			}
	  			*/
#ifdef STATS
	time_t end = time(&end);
	pthread_mutex_lock(&Statistics::CC_Mutex);
	Statistics::CC_calls++;
	Statistics::CC_time += (end-start);
	pthread_mutex_unlock(&Statistics::CC_Mutex);
#endif
	  			return path;
}

string CanonicalConv::toCanonical(const string baseUrl)
{
 		return CanonicalConv::getCanonicalProtocol(baseUrl).append(CanonicalConv::getCanonicalDomain(baseUrl)).append(CanonicalConv::getCanonicalPath(baseUrl));		
}

